from __future__ import division
from __future__ import with_statement
import math
import os
import plant_parser
from player import Player
from field import Field
from plot import Plot
import plant
import pygame
from pygame import Surface
import imagecache
import constants
from plot_type import *
from border import Fence
from world import World
from link import Link
from inventory_item import WateringCan, Seeds
from item import TakeableItem
from building import Building        
from map import Map
import colour
from clock import Clock
from ConfigParser import SafeConfigParser
from custom_exceptions import ConfigError


class Game:
    def __init__(self):
        os.chdir(os.path.split(__file__)[0])
        pygame.init()
        pygame.display.set_caption('Fields and a Mexican Hat')
        self.screen = pygame.display.set_mode((constants.x_res, constants.y_res),  pygame.DOUBLEBUF | pygame.HWSURFACE)
        
        self.plant_dict = plant_parser.init_plants('../data/plants.xml')
        
        self.player = Player()
        (self.player.x, self.player.y) = (160,40)
        self.player.direction = 'down'
        
        self.world = World()
        self.map = self.world.get('farm')
        
        # not used for anything useful presently
        self.turn_number = 1        
        
        # initialise self.left_limit and self.top_limit
        self.position_display()
        
        # optional displays
        self.show_money = True
        self.show_inventory = True
        self.show_fps = False
        self.show_time = True
        
        # font used for displaying money and fps
        self.font = pygame.font.SysFont(pygame.font.get_default_font(), 32)
        
        # various time functions
        self.time = pygame.time.get_ticks()
        self.transition_time = 0
        self.frames_this_second = 0
        self.fps_time = -1001
        self.clock = Clock(6,0)
        self.last_overlay = -1
        
        # used to check if the amount of gold has changed; will only re-render gold if it has
        self.old_gold = -1 
        
        # moving is done in integers, this counts the sub-integer movement_amount
        self.to_move = 0 
        
        #Load controls for game_change
        with open("../data/controls.ini") as controls:
            self.control_conf = SafeConfigParser()
            self.control_conf.readfp(controls)
        if not self.control_conf.has_section('CUSTOM'):
            self.control_conf.add_section('CUSTOM')
        self.conf_dict = {}
        for key, val in self.control_conf.items('CUSTOM'):
            try:
                self.conf_dict[key] = getattr(pygame, "K_" + val)
            except AttributeError:
                try:
                    self.conf_dict[key] = getattr(pygame, "K_" + self.control_conf.defaults()[key])
                except AttributeError:
                    raise ConfigError("Invalid key identifier")
        missing_controls = (set(constants.controls) - set(self.conf_dict.keys()))
        if missing_controls:
            raise ConfigError("Missing the following controls: %s" % ''.join(repr(control) for control in missing_controls))
        
        print self.conf_dict
        
    def run(self):
        while True:
            self.oldtime = self.time
            self.time = pygame.time.get_ticks()
            
            # number of real world ms for 1 game minute
            time_coefficient = 25
            
            real_change = (self.time - self.oldtime)
            game_change = real_change / time_coefficient
            
            self.clock.add(0, game_change)
            
            #print self.clock
            
            if self.transition_time > 0:
                self.transition_time -= self.time - self.oldtime
                if self.transition_time <= 0:
                    self.transition_time = 0
                    self.transition_surface = None
            
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    return
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_ESCAPE:
                        return
                    elif event.key == self.conf_dict['move_down']:
                        self.player.direction = 'down'
                        self.player.moving = True
                    elif event.key == self.conf_dict['move_up']:
                        self.player.direction = 'up'
                        self.player.moving = True
                    elif event.key == self.conf_dict['move_left']:
                        self.player.direction = 'left'
                        self.player.moving = True
                    elif event.key == self.conf_dict['move_right']:
                        self.player.direction = 'right'
                        self.player.moving = True
                    elif event.key == self.conf_dict['skip_day']:
                        self.end_turn()
                    elif event.key == self.conf_dict['interact']:
                        self.player.moving = False
                        self.map.interact(self.player.get_front(), self)
                    elif event.key == self.conf_dict['prev_inventory_item']:
                        self.player.inventory.previous()
                    elif event.key == self.conf_dict['next_inventory_item']:
                        self.player.inventory.next()
                    elif event.key == self.conf_dict['use_item']:
                        self.player.inventory.get().use(self)
                    elif event.key == self.conf_dict['toggle_money']:
                        self.show_money = not self.show_money # toggle money display on and off
                    elif event.key == self.conf_dict['toggle_inventory']:
                        self.show_inventory = not self.show_inventory # toggle inventory display on and off
                    elif event.key == self.conf_dict['toggle_fps']:
                        self.show_fps = not self.show_fps
                    elif event.key == self.conf_dict['toggle_time']:
                        self.show_time = not self.show_time
                    elif event.key == self.conf_dict['print_pos']:
                        print self.player.get_front()
                    elif event.key == self.conf_dict['menu']:
                        self.show_menu()
                elif event.type == pygame.KEYUP:
                        if event.key == pygame.K_DOWN and self.player.direction == 'down':
                            self.player.moving = False
                        elif event.key == pygame.K_UP and self.player.direction == 'up':
                            self.player.moving = False
                        elif event.key == pygame.K_LEFT and self.player.direction == 'left':
                            self.player.moving = False
                        elif event.key == pygame.K_RIGHT and self.player.direction == 'right':
                            self.player.moving = False
            
            # done so that the movement rate is independent of how long it takes to redraw
            # time in ms it should take for the player to move 1 pixel
            movement_rate = 10
            self.to_move += (self.time - self.oldtime)/movement_rate
            movement_amount = int(self.to_move)
            self.to_move -= movement_amount
            
            if self.player.moving:
                # # #
                # move the player if possible
                # # #
                
                # alias for clarity
                can_move = lambda: self.map.can_walk_on(self.player.get_rect())
                
                if self.player.direction == 'left':
                    # move player
                    self.player.x -= movement_amount
                    
                    # unmove if out of bounds
                    if self.player.get_x() < 0:
                        self.player.x = 0
                    
                    # unmove if moved into tile that you can't move into
                    if not can_move():
                        self.player.x += movement_amount
                
                if self.player.direction == 'right':
                    # move player
                    self.player.x += movement_amount
                    
                    # unmove if out of bounds
                    if self.player.get_x() > self.map.width:
                        self.player.x = self.map.width - self.player.player_width
                    
                    # unmove if moved into tile that you can't move into
                    if not can_move():
                        self.player.x -= movement_amount
                
                if self.player.direction == 'up':
                    # move player
                    self.player.y -= movement_amount
                    
                    # unmove if out of bounds
                    if self.player.get_y() < 0:
                        self.player.y = 0
                        
                    # unmove if moved into tile that you can't move into
                    if not can_move():
                        self.player.y += movement_amount
                
                if self.player.direction == 'down':
                    # move player
                    self.player.y += movement_amount
                    
                    # unmove if out of bounds
                    if self.player.get_y() > self.map.height:
                        self.player.y = self.map.height - self.player.player_height
                        
                    # unmove if moved into tile that you can't move into
                    if not can_move():
                        self.player.y -= movement_amount
                
                # if the player has stood on a link, transport them
                link = self.map.get_link(self.player.get_front())
                if link:
                    self.transition_surface = Surface((constants.x_res, constants.y_res))
                    self.draw(self.transition_surface, True)
                    self.transition_time = 500
                    self.transport_player(link)
            
            # # #
            # alter the display limits if the player walks off the screen
            # only if there is enough screen to scroll about on
            # # #
            
            # the ratio of the screen which will trigger scrolling; above 0.5 won't work
            border_ratio = 0.25
            
            if constants.x_res < self.map.width:                
                # left border
                if self.player.x < (self.left_limit + border_ratio*constants.x_res):
                    self.left_limit -= movement_amount
                    if self.left_limit < 0:
                        self.left_limit = 0
                
                # right border
                if self.player.x > (self.left_limit + (1-border_ratio)*constants.x_res):
                    self.left_limit += movement_amount
                    if self.left_limit > self.map.width - constants.x_res:
                        self.left_limit = self.map.width - constants.x_res
            
            if constants.y_res < self.map.height:
                #top border
                if self.player.y < (self.top_limit + border_ratio * constants.y_res):
                    self.top_limit -= movement_amount
                    if self.top_limit < 0:
                        self.top_limit = 0
                
                # bottom border
                if self.player.y > (self.top_limit + (1-border_ratio)*constants.y_res):
                    self.top_limit += movement_amount
                    if self.top_limit > self.map.height - constants.y_res:
                        self.top_limit = self.map.height - constants.y_res
                
            self.draw(surface=self.screen)

    # draw the image of the current state of the game to the specified surface
    def draw(self, surface = None, map_only = False):
        
        map_image = self.map.get_image((self.left_limit, self.top_limit), hour=self.clock.get()[0])
        
        #map_image = Surface((constants.x_res, constants.y_res))
        #map_image.fill(colour.GREEN)
        
        
        # give the background a black surface if the map isn't big enough to cover it
        if map_image.get_width() < constants.x_res or map_image.get_height() < constants.y_res:
            surface.fill(colour.BLACK)
            surface.blit(self.map.get_image(), (self.left_limit,self.top_limit))
        else: 
            surface.blit(map_image, (0,0))
        
        # for pretty transitions when moving between places
        if surface == self.screen:
            if self.transition_time > 0:
                self.transition_surface.set_alpha(int(self.transition_time / 500 * 255))
                surface.blit(self.transition_surface, (0, 0))
        
        if not map_only:
            # draw player to screen; extra stuff in case the map is too small
            x_position = self.player.x - self.left_limit
            y_position = self.player.y - self.top_limit
            
            if self.map.width < constants.x_res:
                x_position = self.player.x + self.left_limit
            if self.map.height < constants.y_res:
                y_position = self.player.y + self.top_limit
            surface.blit(self.player.get_image(), (x_position, y_position))
            
            # draw inventory to screen
            if self.show_inventory:
                inventory_surface = self.player.inventory.get_image()
                surface.blit(inventory_surface, (10, constants.y_res - 10 - inventory_surface.get_height()))        
            
            # draw money to screen
            if self.show_money:
                if self.old_gold != self.player.get_money():
                    self.money_surface = self.font.render(str(self.player.get_money()) + ' gold', True, (255, 255, 255))
                    self.old_gold = self.player.get_money()
                x_co = constants.x_res - 10 - self.money_surface.get_width()
                y_co = constants.y_res - 10 - self.money_surface.get_height()
                surface.blit(self.money_surface, (x_co, y_co))
                
            # draw time
            if self.show_time:
                time_surface = self.font.render(str(self.clock), True, (255, 255, 255))
                surface.blit(time_surface, (0, 0))
                
            # draw fps
            if self.show_fps:
                if self.time > (self.fps_time + 1000):
                    self.fps_surface = self.font.render(str(self.frames_this_second) + ' FPS', True, (255, 255, 255))
                    self.frames_this_second = 0
                    self.fps_time = self.time
                x_co = constants.x_res - 10 - self.fps_surface.get_width()
                y_co = 10
                surface.blit(self.fps_surface, (x_co, y_co))
        
        
        # update with changes
        if surface == self.screen:
            pygame.display.flip()
            self.frames_this_second += 1
    
    # end the turn
    def end_turn(self):
        self.blank_screen(200)
        self.map.end_turn()
        self.turn_number += 1
        self.clock.reset()
    
    # move a player through the link to a new field
    def transport_player(self, link):
        self.map = self.world.get(link.destination)
        self.player.transport(link)
        
        self.position_display()
        
    # adjust the display limits to make the player in middle of screen if possible, as close to middle otherwise
    def position_display(self):
        self.left_limit = self.player.x - 0.5 * (constants.x_res)
        # if field smaller than resolution, centre the field
        if((constants.x_res - self.map.width) // 2 > 0):
            self.left_limit = (constants.x_res - self.map.width) // 2        
        # if showing black on left
        elif(self.left_limit < 0):
            self.left_limit = 0
        # if showing black on right
        elif(self.left_limit > self.map.width - constants.x_res):
            self.left_limit = self.map.width - constants.x_res        
        
        self.top_limit = self.player.y - 0.5 * (constants.y_res)
        # if field smaller than resolution, centre the field
        if((constants.y_res - self.map.height) // 2 > 0):
            self.top_limit = (constants.y_res - self.map.height) // 2        
        # if showing black at top
        elif(self.top_limit < 0):
            self.top_limit = 0
        # if showing black at bottom
        elif(self.top_limit > self.map.height - constants.y_res):
            self.top_limit = self.map.height - constants.y_res
    
    # called when the player presses the interact key
    def interact(self):
        print self.player.get_tile_x(), self.player.get_tile_y()
        for item in self.field.get(self.player.get_tile_x(), self.player.get_tile_y()).get_items():
            item.interact(self)
    
    # make the screen blank for some time in ms
    def blank_screen(self, time):
        blank = Surface((constants.x_res, constants.y_res))
        blank.fill((0,0,0))
        self.screen.blit(blank, (0,0))
        pygame.display.update()
        pygame.time.wait(time)
    
    # works but really slow
    def set_time_overlay(self):
        hour = self.clock.get()[0]
        
        if hour > 10 and hour < 2:
            return
        
        if hour <= self.last_overlay:
            print "x"
            return self.time_overlay
        
        self.time_overlay = Surface((constants.x_res, constants.y_res))
        self.last_overlay = hour
        
        if hour < 10:
            self.time_overlay.fill(colour.ratio(colour.PINK, colour.GOLD, 0.5))
            
        self.time_overlay.set_alpha(50)
        self.time_overlay.convert_alpha()
        return self.time_overlay
    
    # TODO: make this better later
    def show_menu(self):
        self.player.moving = False
        import time
        
        options = ["Tomato Seeds", "Potato Seeds", "Corn Seeds", "Exit"]
        prices = [30, 50, 70]
        options_surfaces = [self.font.render("%s (%i)" % (options[i], prices[i]), True, (255, 255, 255)) for i in range(len(options)-1)]
        options_surfaces.append(self.font.render("Exit", True, (255,255,255)))
        
        selected = 0
        quantity = 1
        
        def choose(number):
            if options[number] == "Exit":
                return
            else:
                total = prices[number] * quantity
                if total > self.player.money:
                    #self.screen.blit(self.font.render("Not enough money", True, (255, 128, 128)), (300, 0))
                    #pygame.display.update()
                    #pygame.time.wait(2000)
                    pass
                else:
                    if(selected == 0):
                        item = Seeds('tomato')
                    elif(selected == 1):
                        item = Seeds('potato')
                    elif(selected == 2):
                        item = Seeds('corn')
                    self.player.money -= total
                    self.player.inventory.add(item, quantity)
        
        from display_window import Menu, Alert
        
        def get_display():
            temp = Surface((constants.x_res, constants.y_res))
            self.draw(temp)
            return temp
        l = (("Tomato seeds (20g)", None, lambda: choose(0)), ("Drop 1 gold",None, lambda: self.player.add_money(-1)), ("Print",None, lambda: funprint("words")), ("Exit",None, None))
        Menu(display=self.screen, contents=l, get_display=get_display, x=100, y=100).show()

    def get_display(self):
        temp = Surface((constants.x_res, constants.y_res))
        self.draw(temp)
        return temp
    
if __name__ == '__main__':
    try:
        game = Game()
    except ConfigError, err:
        print "Invalid configuration file. Please restore it or redownload it.\n (%s)" % err
    else:
        game.player.add_money(500)
        game.player.inventory.add(WateringCan())
        game.run()

